
/**
 * @fileoverview Calendar control.
 * @link http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
 * @link https://developers.google.com/closure/compiler/docs/js-for-compiler
 */



/**
 * Constructor of Calendar.
 * @param {string|Node} container The HTML container or its ID.
 * @constructor
 * @requires formatters.DateFormatter
 * @requires locale.Calendar
 * @example
 * &lt;style>
 * table.calendar {border: solid 1px gray; border-collapse: collapse;}
 * table.calendar thead {background: silver;}
 * table.calendar td,
 * table.calendar th {border: solid 1px gray;}
 * table.calendar td.today {font-weight: bold; background: silver;}
 * table.calendar td.active {font-weight: bold; background: blue; color: white;}
 * table.calendar th.prev,
 * table.calendar th.next {cursor: pointer;}
 * &lt;/style>
 * &lt;div id="calendar-container">&lt;/div>
 * &lt;script>controls.Calendar.draw('calendar-container');&lt;/script>
 * <style>
 * table.calendar {border: solid 1px gray; border-collapse: collapse;
 *                 background: white; font-family: Arial; font-size: 13px;}
 * table.calendar thead {background: silver;}
 * table.calendar td,
 * table.calendar th {border: solid 1px gray;}
 * table.calendar td.today {font-weight: bold; background: silver;}
 * table.calendar td.active {font-weight: bold; background: blue; color: white;}
 * table.calendar th.prev,
 * table.calendar th.next {cursor: pointer;}
 * </style>
 * <script src="../../bin/jscodebase.js"></script>
 * <div id="calendar-container"></div>
 * <script>controls.Calendar.draw('calendar-container')</script>
 */
controls.Calendar = function(container) {

  /**
   * <code>protected</code> The reference to HTML chart container.
   * @type {Node}
   * @protected
   */
  this.container = typeof container == 'string' ?
      dom.getElementById(container) : container;

  /**
   * Draws calendar into specified <code>container</code>.
   * @param {Array.<Date>=} opt_selected Optional list of selected dates.
   * @param {function(HTMLTableElement)=} opt_callback Optional callback.
   * @return {!HTMLTableElement} Returns reference to HTML table element.
   */
  this.draw = function(opt_selected, opt_callback) {
    opt_selected = opt_selected || [new Date];

    selected_[0] = selected_[0] || new Date(opt_selected[0].getTime());
    for (/** @type {number} */ var i = 1; i < opt_selected.length; i++) {
      selected_[i] = new Date(opt_selected[i].getTime());
    }

    /** @type {!Date} */ var date = opt_selected[0];
    self_.container.innerHTML = '<table class="calendar skiptranslate">' +
                                thead_(date) +
                                tbody_(date) + '</table>';
    /** @type {HTMLTableElement} */
    var table = /** @type {HTMLTableElement} */
        (dom.getElementsByTagName(self_.container, 'TABLE')[0]);
    /** @type {HTMLTableRowElement} */
    var row = table.rows[table.rows.length - 1];
    /** @type {string} */ var empty = '';
    for (i = 0; i < row.cells.length;)
      empty += row.cells[i++].innerHTML;
    if (empty == '') table.deleteRow(table.rows.length - 1);
    initNav_(table, date, opt_callback);
    opt_callback && opt_callback(table);
    return table;
  };

  // Export for closure compiler.
  this['draw'] = this.draw;

  /**
   * Iterates over table cells.
   * @param {!function(Element)} callback Iterator callback function.
   */
  this.iterate = function(callback) {
    /** @type {HTMLTableElement} */
    var table = /** @type {HTMLTableElement} */
        (dom.getElementsByTagName(self_.container, 'TABLE')[0]);
    /** @type {Array|NodeList} */
    var cells = dom.getElementsByTagName(table, 'TD') || [];
    for (/** @type {number} */ var i = 0; i < cells.length;) {
      callback(cells[i++]);
    }
  };

  /**
   * Clears today, selected and active days.
   */
  this.clear = function() {
    selected_ = [];
    self_.iterate(function(cell) {
      cell.className = cell.className.replace('active', ' ');
    });
  };

  /**
   * @param {!HTMLTableElement} table The table element.
   * @param {!Date} date The date object.
   * @param {function(HTMLTableElement)=} opt_callback Optional callback.
   * @private
   */
  function initNav_(table, date, opt_callback) {
    /** @type {!HTMLCollection} */ var cells = table.rows[0].cells;
    for (/** @type {number} */ var i = 0; i < cells.length; i++) {
      if (!i || i == cells.length - 1) {
        /** @type {Element} */ var cell = cells[i];
        dom.events.addEventListener(cell, dom.events.TYPE.CLICK, function(e) {
          e = e || window.event;
          /** @type {Element} */ var target = e.target || e.srcElement;
          date.setMonth(date.getMonth() + (target.cellIndex ? 1 : -1));
          self_.draw([date], opt_callback);
        });
      }
    }
  }

  /**
   * @param {!Date} date The date object.
   * @return {string} Returns <TBODY> html markup.
   * @private
   */
  function tbody_(date) {
    /** @type {!Date} */ var now = new Date;
    /** @type {number} */ var year = date.getFullYear();
    /** @type {number} */ var month = date.getMonth();

    // Fix date bug when current day is 31st.
    /** @type {number} */ var fix = new Date(year, month, 1).getDay() + 1;

    /** @type {Array} */
    var dim = [31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    dim[1] = (((year % 100 != 0) && (year % 4 == 0)) ||
        (year % 400 == 0)) ? 29 : 28;

    /** @type {string} */ var tbody = '<tbody><tr>';
    for (/** @type {number} */ var i = 1; i <= 42; i++) {
      /** @type {number} */
      var day = ((i - fix >= 0) && (i - fix < dim[month])) ? i - fix + 1 : 0;
      /** @type {string} */
      var value = !day ? '' : formatters.DateFormatter.format(
          new Date(year, month, day), 'YYYY-MM-dd');

      /** @type {string} */ var className = getCellCssClass_(date, now, day);
      tbody += '<td align="right" class="' + className + '"' +
               ' value="' + value + '">' + (day || '') + '</td>';
      if (((i) % 7 == 0) && (i < 36)) tbody += '</tr><tr>';
    }
    tbody += '</tr></tbody>';
    return tbody;
  }

  /**
   * @param {!Date} date The date object.
   * @return {string} Returns <THEAD> html markup.
   * @private
   */
  function thead_(date) {
    /** @type {!Array} */
    var days = locale_.getWeekNames();
    /** @type {string} */
    var thead = '<thead><tr><th class=prev>&lt;</th><th colspan=5>' +
                locale_.getMonthName(date) + ' ' + date.getFullYear() +
                '</th><th class=next>&gt;</th></tr><tr>';
    for (/** @type {number} */ var i = 0; i < 7;) {
      thead += '<th>' + days[i++] + '</th>';
    }
    thead += '</tr></thead>';
    return thead;
  }


  /**
   * @param {!Date} date The calendar date.
   * @param {!Date} now The current date.
   * @param {number} day The calendar day.
   * @return {string} Returns cell css class for calendar <code>day</code>.
   * @private
   */
  function getCellCssClass_(date, now, day) {
    /** @type {number} */ var year = date.getFullYear();
    /** @type {number} */ var month = date.getMonth();

    /** @type {number} */
    var today = (year == now.getFullYear() &&
                 month == now.getMonth()) ? now.getDate() : 0;

    /** @type {number} */ var active = 0;
    for (/** @type {number} */ var i = 0; i < selected_.length; i++) {
      /** @type {!Date} */ var selected = selected_[i];
      if (year == selected.getFullYear() &&
          month == selected.getMonth() &&
          day == selected.getDate()) {
        active = day;
        break;
      }
    }

    return day ? (day == today ? 'today' : (active ? 'active' : '')) : '';
  }

  /**
   * The reference to current class instance. Used in private methods.
   * @type {!controls.Calendar}
   * @private
   */
  var self_ = this;

  /**
   * @type {!locale.Calendar}
   * @private
   */
  var locale_ = new locale.Calendar;

  /**
   * @type {!Array.<!Date>}
   * @private
   */
  var selected_ = [];
};


/**
 * Draws calendar into specified <code>container</code>.
 * @param {Node|string} container The HTML container or its ID.
 * @param {Array.<Date>=} opt_selected Optional list of selected dates.
 * @return {Element} Returns reference to html table element.
 * @static
 */
controls.Calendar.draw = function(container, opt_selected) {
  return new controls.Calendar(container).draw(opt_selected);
};

// Export for closure compiler.
controls.Calendar['draw'] = controls.Calendar.draw;

// Export for closure compiler.
controls['Calendar'] = controls.Calendar;

